﻿#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif
using System;
using System.Windows;
#if WINDOWS_PHONE
using System.Windows.Navigation;
#endif
using JetBrains.Annotations;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the <see cref="IBootstrapper" /> which used to start mvvm-application.
    /// </summary>
    public class Bootstrapper<TRootViewModel> : Bootstrapper where TRootViewModel :
#if WPF
 IWindowViewModel
#endif
#if WINDOWS_PHONE || NETFX_CORE
 IViewModel
#endif
#if SILVERLIGHT && !WINDOWS_PHONE
 IHasView
#endif
    {
        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="Bootstrapper{TRootViewModel}" /> class.
        /// </summary>
        protected Bootstrapper([CanBeNull]IInitializationStrategy initializationStrategy)
            : base(typeof(TRootViewModel), initializationStrategy)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Bootstrapper{TRootViewModel}" /> class.
        /// </summary>
        public Bootstrapper([NotNull]IIocAdapter iocAdapter, IInitializationStrategy initializationStrategy = null)
            : base(typeof(TRootViewModel), iocAdapter, initializationStrategy)
        {
        }

        #endregion
    }

    /// <summary>
    ///     Represents the <see cref="IBootstrapper" /> which used to start mvvm-application.
    /// </summary>
    public partial class Bootstrapper : IBootstrapper
    {
        #region Fields

        private readonly object _locker;
        private readonly Type _mainViewModelType;
        private IIocAdapter _iocAdapter;
        private bool _isLoaded;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="Bootstrapper" /> class.
        /// </summary>
        protected Bootstrapper([NotNull]Type mainViewModelType, [CanBeNull] IInitializationStrategy initializationStrategy)
        {
            Should.NotBeNull(mainViewModelType, "mainViewModelType");
            Should.BeViewModel(mainViewModelType, "mainViewModelType");
            if (initializationStrategy == null)
                initializationStrategy = new DefaultInitializationStrategy();
            _locker = new object();
            _mainViewModelType = mainViewModelType;
            InitializationStrategy = initializationStrategy;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Bootstrapper" /> class.
        /// </summary>
        public Bootstrapper([NotNull]Type mainViewModelType, [NotNull] IIocAdapter iocAdapter, IInitializationStrategy initializationStrategy = null)
            : this(mainViewModelType, initializationStrategy)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            _iocAdapter = iocAdapter;
        }

        #endregion

        #region Implementation of IBootstrapper

        /// <summary>
        ///     Gets the initialized state of the current bootstrapper.
        /// </summary>
        public bool IsStarted
        {
            get
            {
                using (DeadLockDetector.Lock(_locker))
                    return _isLoaded;
            }
            protected set { _isLoaded = value; }
        }

        /// <summary>
        ///     Gets the root <see cref="IIocAdapter" />.
        /// </summary>
        public IIocAdapter IocAdapter
        {
            get
            {
                Should.PropertyBeNotNull(_iocAdapter, "IocAdapter");
                return _iocAdapter;
            }
            protected set
            {
                Should.PropertyBeNotNull(value, "IocAdapter");
                _iocAdapter = value;
            }
        }

        /// <summary>
        ///     Starts the current <see cref="IBootstrapper" />.
        /// </summary>
        public virtual void Start()
        {
            Application application = Application.Current;
// ReSharper disable once NotResolvedInText
            Should.NotBeNull(application, "Application.Current");
            IDisposable @lock = null;
            try
            {
                @lock = DeadLockDetector.Lock(_locker);
                if (IsStarted) return;
                Initialize();
                GlobalSettings.SetRootIocAdapterWithSettings(IocAdapter);
                IActivationPolicy policy = GetMainViewModelActivationPolicy() ?? GlobalSettings.DefaultActivationPolicy;
                var viewModelProvider = IocAdapter.Get<IViewModelProvider>();
#if WPF
                Should.BeViewModel(MainViewModelType, "MainViewModelType", typeof(IWindowViewModel));
                var viewModel = (IWindowViewModel)viewModelProvider.GetViewModel(MainViewModelType, policy);
#if TASKSUPPORT
                viewModel.ShowAsync(policy.ViewBindingName)
                    .ContinueWith(result => viewModel.Dispose());
#else
                viewModel.ShowAsync(policy.ViewBindingName)
                    .BeginWait(result => viewModel.Dispose());
#endif

#endif

#if WINDOWS_PHONE
                Should.BeViewModel(MainViewModelType, "MainViewModelType", typeof(IViewModel));
                MvvmApplication.RootFrame = new Microsoft.Phone.Controls.PhoneApplicationFrame();
                MvvmApplication.RootFrame.Navigated += CompleteInitializePhoneApplication;

                var navigationService = new FrameNavigationService(MvvmApplication.RootFrame);
                navigationService.RootItem = IocAdapter
                    .Get<IViewPageMappingProvider>()
                    .FindMappingForViewModel(MainViewModelType, policy.ViewBindingName, true);
                NavigationProvider = GetNavigationProvider(navigationService);
                _iocAdapter.BindToConstant(NavigationProvider);
                OnNavigationProviderInitialized();

                if (!navigationService.NavigateToRoot())
                    throw new InvalidOperationException("Failed to create initial page.");
                return;
#endif
#if SILVERLIGHT && !WINDOWS_PHONE
                Should.BeViewModel(MainViewModelType, "MainViewModelType", typeof(IHasView));
                var mainViewModel = (IHasView)viewModelProvider.GetViewModel(MainViewModelType, policy);
                Should.NotBeNull(mainViewModel.View, "mainViewModel.View");
                application.RootVisual = (UIElement)mainViewModel.View;
#endif
#if NETFX_CORE
                Should.BeViewModel(MainViewModelType, "MainViewModelType", typeof(IViewModel));
                var frame = new Frame();
                Window.Current.Content = frame;
                var frameViewAdapter = new FrameNavigationService(frame);
                frameViewAdapter.RootItem = IocAdapter
                    .Get<IViewPageMappingProvider>()
                    .FindMappingForViewModel(MainViewModelType, policy.ViewBindingName, true);
                NavigationProvider = GetNavigationProvider(frameViewAdapter);
                IocAdapter.BindToConstant(NavigationProvider);
                OnNavigationProviderInitialized();

                if (!frameViewAdapter.NavigateToRoot())
                    throw new InvalidOperationException("Failed to create initial page.");
                // Ensure the current window is active
                Window.Current.Activate();
#endif
            }
            finally
            {
                IsStarted = true;
                if (@lock != null)
                    @lock.Dispose();
            }
        }

        /// <summary>
        ///     Stops the current <see cref="IBootstrapper" />.
        /// </summary>
        public virtual void Stop()
        {
#if !WPF
            DisposeNavigationProvider();
#endif
            if (_iocAdapter != null)
                _iocAdapter.Dispose();
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the type of main view model.
        /// </summary>
        public Type MainViewModelType
        {
            get { return _mainViewModelType; }
        }

        /// <summary>
        ///     Gets the initialization strategy.
        /// </summary>
        protected IInitializationStrategy InitializationStrategy { get; private set; }

        #endregion

        #region Methods

#if WINDOWS_PHONE
        private static void CompleteInitializePhoneApplication(object sender, NavigationEventArgs navigationEventArgs)
        {
            // Set the root visual to allow the application to render
            if (Application.Current.RootVisual != MvvmApplication.RootFrame)
                Application.Current.RootVisual = MvvmApplication.RootFrame;

            // Remove this handler since it is no longer needed
            MvvmApplication.RootFrame.Navigated -= CompleteInitializePhoneApplication;
        }
#endif
        /// <summary>
        ///     Initializes the current <see cref="IBootstrapper" />.
        /// </summary>
        protected virtual void Initialize()
        {
            IocAdapter = GetIocAdapter();
            InitializationStrategy.Initialize(IocAdapter);
        }

        /// <summary>
        ///     Gets the IActivationPolicy that used to create main view model.
        /// </summary>
        protected virtual IActivationPolicy GetMainViewModelActivationPolicy()
        {
            return GlobalSettings.DefaultActivationPolicy;
        }

        /// <summary>
        ///     Creates the <see cref="IIocAdapter" />.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IIocAdapter" />.
        /// </returns>
        protected virtual IIocAdapter GetIocAdapter()
        {
            Should.MethodBeSupported(_iocAdapter != null, "GetIocAdapter()");
            return _iocAdapter;
        }

        #endregion
    }
}